Skip to content

make the green backend the default on linux - #599

Merged
kacy merged 4 commits into
mainfrom
green-default-linux
Jul 27, 2026
Merged

make the green backend the default on linux#599
kacy merged 4 commits into
mainfrom
green-default-linux

Conversation

@kacy

@kacy kacy commented Jul 27, 2026

Copy link
Copy Markdown
Owner

summary

green is now the default task backend on linux. os threads stay the default
everywhere else, and PITH_GREEN overrides in both directions on any platform.

backend() used to read PITH_GREEN and treat everything that was not
1/on/true as os threads, which meant there was no way to say "off" as
distinct from "unset". that did not matter while os threads were the default; it
does now. the parsing moved into a pure backend_from_env so it can be unit
tested without fighting the OnceLock that backend() caches into, and it reads
1/on/true as green, 0/off/false as os threads, and anything else as
the platform default. values are trimmed and matched case-insensitively, because
the off switch is the escape hatch for anyone the flip hurts and should not be
missable by a capital letter.

unrecognized values fall to the platform default rather than to os threads. the
rule that comes out of that is short enough to hold in your head: the only values
that move the backend are the six spelled out in the match, and a typo like
PITH_GREEN=yes behaves exactly like an unset variable. falling to os threads
instead would have made a typo silently disable the platform default, which is
the more surprising of the two.

the default is linux-only because the reactor is. netpoll.rs is epoll and
eventfd behind #[cfg(target_os = "linux")]; macos and the bsds compile
netpoll_fallback.rs, which has no reactor at all, so a green task waiting on a
socket there holds its worker for the whole wait. green stays reachable on those
platforms with PITH_GREEN=1, just not by default.

what changed outside the runtime

a silent flip does most of its damage in the places that compared the two
backends by running one of them without saying so. every green-* target in the
Makefile ran its reference side as a bare pith run; on a linux box that is now
the green side, so the differentials were about to compare green against green
and pass for free. all twelve now run their os-thread side under PITH_GREEN=0.

make test on linux likewise exercises only green now, which left the
PITH_GREEN=0 path across the corpus with no gate at all. verify-osthread-corpus
mirrors verify-green-corpus for the opt-out and ci runs both;
verify-green-corpus itself was already explicit about its backend and still
tests what it says, with the added value that it holds on a mac too.

memcheck had the same hole in miniature: test_os_thread_spawn_reclaim exists to
valgrind the os-thread slab reclaim path and would have started running green.
it and three other task-lifecycle cases now get a second pass under
PITH_GREEN=0, alongside the green pass they get from the default.

bench/chan_fanout_bench.sh already set PITH_GREEN explicitly on both rows, so
only its prose and the pith row label needed to change.

docs

docs/concurrency.md drops the "off by default and still experimental" framing;
the backend-choice section now describes green as the linux default with
PITH_GREEN=0 as the opt-out. docs/limitations.md had a list of what it would
take to make green the default, which is mostly history, so it reads as the
caveats of the current default instead. the performance scoreboard labelled its
concurrency rows "(green)"; the os-thread rows are the ones that need a label
now.

the caveats stay documented in both places rather than buried. file i/o has no
yield point: host_fs goes straight to the syscall, so a file read holds its
worker and everything pinned to it, and a program can hit that purely by
upgrading. preemption is a build-time opt-in (PITH_GREEN_PREEMPT=1) where the
kernel gave os threads preemption for free. task placement is first-resume luck,
which is why the fan-out benchmark is bimodal at ~46ms pinned against ~130-170ms
split.

what was tested

cargo build --release at the repo root, then cargo test --release --workspace --locked: 115 tests pass, of which 98 are the runtime (up from 93, the six new
backend_from_env cases replacing the single old default assertion).

make test under the new default, on this linux box, so green: 99 examples, 261
regressions, 3 invalid-parse, 37 invalid-checker, 10 cli, 6 ir-contract, then 261
self-hosted regressions, 3 self-hosted invalid-parse, 14 parity, 37 self-hosted
invalid-checker, 5 self-hosted cli. zero failures. PITH_GREEN=0 make test
produces the identical counts, also with zero failures. nothing that passed under
os threads fails under green.

make green-tests (all eleven), make verify-green-corpus (261 cases at the
default worker count and again at one worker), the new
make verify-osthread-corpus (261 cases), make run-examples (99),
make docsite-check, and make memcheck, which is valgrind-clean including the
four cases that now get a second os-thread pass.

kacy added 4 commits July 27, 2026 04:43
green is faster on every shape this repo measures and the last blocker
(dns holding a worker) landed in #598, so a spawned task now runs as a
coroutine on the worker pool unless you say otherwise.

the default stays os threads everywhere else. the reactor is epoll and
eventfd, so macos and the bsds compile netpoll_fallback, which has no
reactor at all: a green task waiting on a socket there would hold its
worker for the whole wait. green is still reachable with PITH_GREEN=1 on
those platforms, just not the default.

PITH_GREEN now reads in both directions. 1/on/true force green, 0/off/false
force os threads, and anything else takes the platform default. the off
side is new and is the escape hatch for anyone the flip hurts, so it is
trimmed and matched case-insensitively. an unrecognized value falls to the
platform default rather than to os threads, so the only values that move
the backend are the ones spelled out in the match and a typo behaves
exactly like an unset variable.

parsing moved into a pure backend_from_env so it can be tested without
fighting the OnceLock that backend() caches into.
with green as the linux default, a comparison whose reference side just
says `pith run` is green against green and passes for free. every green-*
target now runs its os-thread side under PITH_GREEN=0.

the same reasoning applies to coverage. `make test` on a linux box now
exercises only green, so the PITH_GREEN=0 path across the corpus had no
gate at all. verify-osthread-corpus mirrors verify-green-corpus for the
opt-out and ci runs both. memcheck grew a small second list for the cases
whose subject is the os-thread task machinery itself, so the flip does not
quietly stop testing the code they were written for.
docs/concurrency.md drops the "off by default and still experimental"
framing. the backend-choice section now says green on linux, os threads
elsewhere, and PITH_GREEN=0 as the opt-out, and it keeps the caveats that
came with the flip rather than burying them: file i/o has no yield point
so a file read holds its worker and everything pinned to it; preemption is
a build-time opt-in where the kernel gave os threads preemption for free;
and task placement is first-resume luck.

docs/limitations.md had a list of what it would take to make green the
default. most of that is history now, so it reads as the caveats of the
current default instead.

the performance scoreboard labelled its concurrency rows "(green)"; the
os-thread rows are the ones that need a label now, so they carry
PITH_GREEN=0. the same for the bench README table and the examples and
module headers that showed PITH_GREEN=1 as the way to opt in.
three small corrections on top of the flip.

`PITH_GREEN=no` fell to the platform default, which on linux is green — so
someone reaching for the escape hatch and writing the most natural word for
"off" got exactly what they were trying to turn off. no/n now mean os threads
and yes/y mean green, which keeps the rule (only spelled-out values move the
backend) while making the direction that costs something hard to miss.

the green corpus target claimed it "matches os-thread output". it compares
against a fixed expected file, which is the stronger check and the reason it
catches a drift both backends share. now that there is a sibling target that
really does run os threads, the old wording is worth correcting.

and the corpus is 261 cases, not 260, since the dns case joined it.
@kacy
kacy merged commit 1f05204 into main Jul 27, 2026
2 checks passed
@kacy
kacy deleted the green-default-linux branch July 27, 2026 05:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant